package com.gugee.ins.data.monitor.aop.spider;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.gugee.ins.common.constant.AiConst;
import com.gugee.ins.common.constant.AiErrorCodeEnum;
import com.gugee.ins.common.constant.FaceRecognitionCodeEnum;
import com.gugee.ins.common.model.ai.FaceRecognition;
import com.gugee.ins.common.model.ai.InsBloggerBrandResponse;
import com.gugee.ins.common.model.ai.InsBloggerCategoryResponse;
import com.gugee.ins.common.model.ai.InsProductCategoryResponse;
import com.gugee.ins.common.model.spider.app.media.InsAppGetProductMediaResponse;
import com.gugee.ins.common.model.spider.app.media.InsAppMediaResponse;
import com.gugee.ins.common.model.spider.blogger.*;
import com.gugee.ins.common.model.spider.comment.InsMediaCommentsResponse;
import com.gugee.ins.common.model.spider.media.InsMediaResponse;
import com.gugee.ins.data.common.constant.EventStatusEnum;
import com.gugee.ins.data.common.constant.EventTypeEnum;
import com.gugee.ins.data.common.msg.EventMsg;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Component;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Created with IntelliJ IDEA.
 * Description:对调用接口的RestTemplate类方法进行监控
 * User: luolifeng
 * Date: 2021-01-08 12:11
 */
@Aspect
@Component
@Slf4j
public class SpiderAspect {
    @Autowired
    KafkaTemplate<Long, EventMsg> kafkaTemplateMonitor;
    @Value("${kafka.topics.insMonitor.online}")
    String topic;
    /**
     * 切入点
     * 匹配org.springframework.web.client包及其子包下的所有类的所有方法
     * @Pointcut("execution(* org.springframework.web.client.*.*(..))")
     */
    @Pointcut("execution(* org.springframework.web.client.RestTemplate.getForEntity(..)) || execution(* org.springframework.web.client.RestTemplate.postForEntity(..)) || execution(* org.springframework.web.client.RestTemplate.exchange(..))")
    public void pointCut(){

    }

    /**
     * 前置通知，目标方法调用前被调用
     */
//    @Before("pointCut()")
//    public void beforeAdvice(JoinPoint joinPoint){
//        System.out.println("----------- 前置通知 -----------");
//        Signature signature = joinPoint.getSignature();
//        System.out.println("返回目标方法的签名：" + signature);
//        System.out.println("代理的是哪一个方法：" + signature.getName());
//        Object[] args = joinPoint.getArgs();
//        System.out.println("获取目标方法的参数信息：" + Arrays.asList(args));
//
//    }

    /**
     * 最终通知，目标方法执行完之后执行
     */
//    @After("pointCut()")
//    public void afterAdvice(){
//        System.out.println("----------- 最终通知 -----------");
//
//    }

    /**
     * 后置返回通知
     * 如果参数中的第一个参数为JoinPoint，则第二个参数为返回值的信息
     * 如果参数中的第一个参数不为JoinPoint，则第一个参数为returning中对应的参数
     * returning 只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知，否则不执行
     * @param joinPoint
     * @param keys
     */
//    @AfterReturning(value = "pointCut()", returning = "keys")
//    public void afterReturningAdvice(JoinPoint joinPoint, InsBloggerByIdResponse keys){
//        System.out.println("后置返回通知的返回值：" + keys);
//    }

    /**
     * 后置异常通知
     * 定义一个名字，该名字用于匹配通知实现方法的一个参数名，当目标方法抛出异常返回后，将把目标方法抛出的异常传给通知方法；
     * throwing 只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知，否则不执行，
     * @param joinPoint
     * @param e
     */
//    @AfterThrowing(value = "pointCut()", throwing = "e")
//    public void afterThrowingAdvice(JoinPoint joinPoint, NullPointerException e){
//        System.out.println("----------- 后置异常通知 -----------");
//    }

//    @AfterThrowing(value = "pointCut()", throwing = "throwable")
//    public void doAfterThrowing(Throwable  throwable){
//        System.out.println("----------- 后置异常通知 -----------");
//
//        System.out.println("----------- 后置异常通知 -----------"+throwable.getMessage());
//    }
//    @AfterThrowing(value = "pointCut()", throwing = "e")
//    public void afterThrowingAdvice2(JoinPoint joinPoint, Throwable e){
//        System.out.println("----------- 后置异常通知2 -----------");
//
//        System.out.println("----------- 后置异常通知2 -----------"+e.getMessage());
//    }
    /**
     * 环绕通知
     * 环绕通知非常强大，可以决定目标方法是否执行，什么时候执行，执行时是否需要替换方法参数，执行完毕是否需要替换返回值。
     * 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
     * @param proceedingJoinPoint
     */
    @Around("pointCut()")
    public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.debug("----------- 环绕通知 -----------");
        Throwable e=null;
        Object proceed =null;
        long start=System.currentTimeMillis();
        try {
            proceed = proceedingJoinPoint.proceed();
            return proceed;
        } catch (Throwable throwable) {
            e=throwable;
            throw throwable;
        }finally {
            aopHandler(proceedingJoinPoint,e,start,proceed);
            log.debug("----------- 环绕通知结束 -----------");
        }
    }
    private void aopHandler(ProceedingJoinPoint proceedingJoinPoint, Throwable e,long startTime,Object proceed){
        String methodName = proceedingJoinPoint.getSignature().getName();
        //目前只有爬虫接口调用
        if(methodName.equals("getForEntity")) {
            spiderHandler(proceedingJoinPoint, e, startTime, proceed);
        }else if(methodName.equals("postForEntity")){
            //目前只有AI接口调用
            aiHandler(proceedingJoinPoint, e, startTime, proceed);
        }else if(methodName.equals("exchange")){
            //目前只有AI接口调用
            aiHandler(proceedingJoinPoint, e, startTime, proceed);
        }else{

        }
    }
    private void aiHandler(ProceedingJoinPoint proceedingJoinPoint, Throwable e,long startTime,Object proceed){
        try{
            Object[] args = proceedingJoinPoint.getArgs();
            if(args.length<1){
                return;
            }
            List<Object> paramList = new ArrayList<>(Arrays.asList(args));
            EventMsg eventMsg =new EventMsg();
            eventMsg.setEventType(EventTypeEnum.AI_ACCESS);
            /**
             * 获取接口名
             */
            if(!paramList.isEmpty()){
                String url=args[0].toString();
                int end = url.contains("?") ?url.indexOf("?"):url.length();
                int s = url.lastIndexOf("/")+1;
                eventMsg.setOwner(url.substring(s,end));
            }
            eventMsg.setPosition(args[0].toString());
            if(e!=null){
                eventMsg.setContent(e.getClass()+":"+e.getMessage());
                eventMsg.setEventStatus(EventStatusEnum.EXCEPTION.getCode());
            }else if(proceed instanceof ResponseEntity){
                ResponseEntity responseEntity =  (ResponseEntity) proceed;
                if(responseEntity.getStatusCode() != HttpStatus.OK){
                    eventMsg.setEventStatus(EventStatusEnum.EXCEPTION.getCode());
                    eventMsg.setContent(responseEntity.getStatusCode().name());
                }else if(responseEntity.getBody()==null){
                    eventMsg.setEventStatus(EventStatusEnum.AI_BODY_NULL.getCode());
                    eventMsg.setContent("Body为空");
                }else{

                    EventStatusEnum status= getAiEventStatus(responseEntity);
                    eventMsg.setEventStatus(status.getCode());

                    String jsons= JSON.toJSONString(responseEntity.getBody());
                    if("incomeforecas".equals(eventMsg.getOwner())){
                        eventMsg.setContent(MessageFormat.format("{0}#{1}#{2}", responseEntity.getStatusCode().name()
                                ,jsons.length(),jsons.substring(0, Math.min(jsons.length(), 200))));
                    }else{
                        JSONObject jsonObject= JSON.parseObject(jsons);
                        eventMsg.setContent(MessageFormat.format("{0}#{1}#{2}#{3}", responseEntity.getStatusCode().name(),
                                jsonObject.keySet(),jsons.length(),jsons.substring(0, Math.min(jsons.length(), 200))));
                    }
                }
            }else {
                eventMsg.setEventStatus(EventStatusEnum.EXCEPTION.getCode());
                eventMsg.setContent("proceed not instanceof ResponseEntity");
            }
            eventMsg.setSpendMills(System.currentTimeMillis()-startTime);
            eventMsg.setId(System.currentTimeMillis());
            kafkaTemplateMonitor.send(topic,eventMsg);
        } catch(Exception ecp){
            ecp.printStackTrace();
        }
    }
    private void spiderHandler(ProceedingJoinPoint proceedingJoinPoint, Throwable e,long startTime,Object proceed){
      try{
            Object[] args = proceedingJoinPoint.getArgs();
            List<Object> paramList = new ArrayList<>(Arrays.asList(args));
          List<Object>  paramFinalList = new ArrayList<>(5);
            EventMsg eventMsg =new EventMsg();
            eventMsg.setEventType(EventTypeEnum.SPIDER_ACCESS);
          /**
           * 获取接口名
           */
          if(!paramList.isEmpty()){
                String url=args[0].toString();
                int end = url.contains("?") ?url.indexOf("?"):url.length();
                int s = url.lastIndexOf("/")+1;
                eventMsg.setOwner(url.substring(s,end));
            }
          eventMsg.setPosition(paramList.toString());
          /**
           * 获取接口参数
           */
          if(paramList.size()>2){
              paramFinalList.add(paramList.get(0));
              Object[] subArgs = (Object[]) paramList.get(2);
              paramFinalList.addAll(Arrays.asList(subArgs));
              eventMsg.setPosition(paramFinalList.toString());
          }



            eventMsg.setId(System.currentTimeMillis());
            if(e!=null){
                eventMsg.setContent(e.getClass()+":"+e.getMessage());
                eventMsg.setEventStatus(EventStatusEnum.EXCEPTION.getCode());
            }else if(proceed instanceof ResponseEntity){
                  ResponseEntity responseEntity =  (ResponseEntity) proceed;
                   if(responseEntity.getStatusCode() != HttpStatus.OK){
                      eventMsg.setEventStatus(EventStatusEnum.EXCEPTION.getCode());
                      eventMsg.setContent(responseEntity.getStatusCode().name());
                   }else if(responseEntity.getBody()==null){
                       eventMsg.setEventStatus(EventStatusEnum.SPIDER_BODY_NULL.getCode());
                       eventMsg.setContent("Body为空");
                   }else{

                       EventStatusEnum status= getEventStatus(responseEntity);
                       eventMsg.setEventStatus(status.getCode());

                      String jsons= JSON.toJSONString(responseEntity.getBody());
                      JSONObject jsonObject= JSON.parseObject(jsons);
                       if(jsonObject!=null && jsonObject.get("status")!=null && "-9001".equals(jsonObject.get("status").toString())){
                           eventMsg.setEventStatus(EventStatusEnum.SPIDER_COOKIE_LIMIT.getCode());
                       }
                       if(jsonObject!=null && jsonObject.get("status")!=null && "-7404".equals(jsonObject.get("status").toString())){
                           eventMsg.setEventStatus(EventStatusEnum.SPIDER_404.getCode());
                       }
                      eventMsg.setContent(MessageFormat.format("{0}#{1}#{2}#{3}", responseEntity.getStatusCode().name(),
                              jsonObject.keySet(),jsons.length(),jsons.substring(0, Math.min(jsons.length(), 200))));
                  }
              }else {
                  eventMsg.setEventStatus(EventStatusEnum.EXCEPTION.getCode());
                  eventMsg.setContent("proceed not instanceof ResponseEntity");
              }

            eventMsg.setSpendMills(System.currentTimeMillis()-startTime);

            kafkaTemplateMonitor.send(topic,eventMsg);
        } catch(Exception ecp){
          ecp.printStackTrace();
        }
    }
    private EventStatusEnum getEventStatus(ResponseEntity responseEntity){

        try {
            if (responseEntity.getBody() instanceof InsBloggerFollowedResponse) {
                InsBloggerFollowedResponse resp = (InsBloggerFollowedResponse) responseEntity.getBody();
                if (!resp.getData().getUser().getEdgeFollow().getEdges().isEmpty() || resp.getData().getUser().getEdgeFollow().getCount() == 0) {
                    return EventStatusEnum.NORMAL;
                }
            }
            if (responseEntity.getBody() instanceof InsMediaCommentsResponse) {
                InsMediaCommentsResponse resp = (InsMediaCommentsResponse) responseEntity.getBody();
                if (!resp.getData().getShortCodeMedia().getEdgeMediaToParentComment().getEdges().isEmpty() || resp.getData().getShortCodeMedia().getEdgeMediaToParentComment().getCount() == 0) {
                    return EventStatusEnum.NORMAL;
                }
            }
            if (responseEntity.getBody() instanceof InsBloggerFollowingResponse) {
                InsBloggerFollowingResponse resp = (InsBloggerFollowingResponse) responseEntity.getBody();
                if (!resp.getData().getUser().getEdgeFollow().getEdges().isEmpty() || resp.getData().getUser().getEdgeFollow().getCount() == 0) {
                    return EventStatusEnum.NORMAL;
                }
            }
            if (responseEntity.getBody() instanceof InsAppMediaResponse) {
                InsAppMediaResponse resp = (InsAppMediaResponse) responseEntity.getBody();
                if ("ok".equals(resp.getStatus())) {
                    return EventStatusEnum.NORMAL;
                }
            }
            if (responseEntity.getBody() instanceof InsAppGetProductMediaResponse) {
                InsAppGetProductMediaResponse resp = (InsAppGetProductMediaResponse) responseEntity.getBody();
                if ("ok".equals(resp.getStatus())) {
                    return EventStatusEnum.NORMAL;
                }
            }

            if (responseEntity.getBody() instanceof InsMediaResponse) {
                InsMediaResponse resp = (InsMediaResponse) responseEntity.getBody();
                if (!resp.getData().getUser().getEdgeOwnerToTimelineMedia().getEdges().isEmpty()  || resp.getData().getUser().getEdgeOwnerToTimelineMedia().getCount() == 0) {
                    return EventStatusEnum.NORMAL;
                }
            }
            if (responseEntity.getBody() instanceof InsBloggerDetailResponse) {
                InsBloggerDetailResponse resp = (InsBloggerDetailResponse) responseEntity.getBody();
                if (resp.getDateJoined() != null) {
                    return EventStatusEnum.NORMAL;
                }
            }
            if (responseEntity.getBody() instanceof InsBloggerByIdResponse) {
                InsBloggerByIdResponse resp = (InsBloggerByIdResponse) responseEntity.getBody();
                if (resp.getUser() != null) {
                    return EventStatusEnum.NORMAL;
                }
            }
            if (responseEntity.getBody() instanceof InsBloggerByUsernameResponse) {
                InsBloggerByUsernameResponse resp = (InsBloggerByUsernameResponse) responseEntity.getBody();
                if (resp.getGraphql().getUser().getUsername() != null) {
                    return EventStatusEnum.NORMAL;
                }
            }
        }catch (NullPointerException e){
            return EventStatusEnum.SPIDER_PROPERTIES_DEFECT;
        }
        return EventStatusEnum.SPIDER_DATA_DEFECT;
    }

    private EventStatusEnum getAiEventStatus(ResponseEntity responseEntity){

        try {
            if (responseEntity.getBody() instanceof InsBloggerCategoryResponse) {
                InsBloggerCategoryResponse resp = (InsBloggerCategoryResponse) responseEntity.getBody();
                if (resp.getStatus() != AiConst.AI_STATUS_FAIL) {
                    return EventStatusEnum.NORMAL;
                } else{
                    if(resp.getErrorCode() == AiErrorCodeEnum.PARAM_ERROR.getCode()){
                        return EventStatusEnum.AI_PARAM_ERROR;
                    }
                    if(resp.getErrorCode() == AiErrorCodeEnum.IMPL_ERROR.getCode()){
                        return EventStatusEnum.AI_IMPL_ERROR;
                    }
                }
            }
            if (responseEntity.getBody() instanceof FaceRecognition) {
                FaceRecognition resp = (FaceRecognition) responseEntity.getBody();
                if (resp.getErrorCode() == FaceRecognitionCodeEnum.SUCCESS.getCode()) {
                    return EventStatusEnum.NORMAL;
                } else{
                    if(resp.getErrorCode() == FaceRecognitionCodeEnum.PIC_DOWNLOAD_ERROR.getCode()){
                        return EventStatusEnum.AI_PIC_DOWNLOAD_ERROR;
                    }
                    if(resp.getErrorCode() == FaceRecognitionCodeEnum.IMPL_ERROR.getCode()){
                        return EventStatusEnum.AI_IMPL_ERROR;
                    }
                    if(resp.getErrorCode() == FaceRecognitionCodeEnum.QPS_FULL.getCode()){
                        return EventStatusEnum.AI_QPS_FULL;
                    }
                    if(resp.getErrorCode() == FaceRecognitionCodeEnum.PIC_NO_FACE.getCode()){
                        return EventStatusEnum.AI_PIC_NO_FACE;
                    }
                }
            }
            if (responseEntity.getBody() instanceof InsBloggerBrandResponse) {
                return EventStatusEnum.NORMAL;
            }

            if (responseEntity.getBody() instanceof InsProductCategoryResponse) {
                InsProductCategoryResponse insProductCategoryResponse = (InsProductCategoryResponse) responseEntity.getBody();
                if(AiConst.PRODUCT_CATE_UNKNOWN.equals(insProductCategoryResponse.getCate())){
                    return EventStatusEnum.AI_PIC_DOWNLOAD_ERROR;
                }
                return EventStatusEnum.NORMAL;
            }
        }catch (NullPointerException e){
            return EventStatusEnum.AI_PROPERTIES_DEFECT;
        }
        return EventStatusEnum.NORMAL;
    }
}
